﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Security.Permissions;
using Algobox.JuicyLib.Cryptography.GnuPGWrapper;

namespace Algobox.JuicyLib.Apps.MLXDownloader
{
    public class MLXDownloader
    {
        public MLXDownloader(string hostUri, string userName, string password, string archiveDir, string gpgDir, string gpgBin, string dbConn)
        {
            MLX_HOST = hostUri;
            MLX_USER = userName;
            MLX_PASS = password;

            if (archiveDir[archiveDir.Length - 1] != '/')
                MLX_ARCHIVE_DIR = archiveDir + '/';
            else
                MLX_ARCHIVE_DIR = archiveDir;

            GPG_DIR = Path.GetDirectoryName(gpgDir);
            GPG_BIN = Path.GetFullPath(gpgBin);

            m_FTP = new Algobox.JuicyLib.Net.FTP.JuicyFTP(userName, password, hostUri, Algobox.JuicyLib.Net.FTP.JuicyFTP.FTPMode.Unix, Algobox.JuicyLib.Net.FTP.JuicyFTP.FTPCommand.Passive);
            sqlImporter = new Algobox.JuicyLib.SQL.LiquidView.LiquidViewLinqDataContext(dbConn);
                       
        }
                
        readonly string MLX_HOST;
        readonly string MLX_USER;
        readonly string MLX_PASS;
        readonly string MLX_ARCHIVE_DIR;
        
        readonly string GPG_DIR;
        readonly string GPG_BIN;

        const int ML_FILENAME_DATE_LEN = 14;
        const int ML_FILENAME_DATE_PART_LEN = 8;
        const int ML_FILENAME_TIME_PART_LEN = 5;
        const string ML_FILENAME_ASC_SUFFIX = ".asc";

        const string DIR_CENT_PREFIX = "20"; // as in 2009

        readonly Algobox.JuicyLib.Net.FTP.JuicyFTP m_FTP;


        /// <summary>
        /// MS generated SQL class to Liquid View SP's
        /// </summary>
        Algobox.JuicyLib.SQL.LiquidView.LiquidViewLinqDataContext sqlImporter = null;


        public void Log(string action, string fileName, string description)
        {
            Console.WriteLine(description);
            sqlImporter.LogGeneral("MLXDownloader", action, fileName, description, null, null, null, null, null, null);
        }

        public void LogError(string message)
        {
            sqlImporter.ExceptionHandlerThrow("MLXDownloader", message, null);
        }

        //
        // MLX is a UNIX FTP server
        //
        // drwx------ 1 mailbox group           1024 Jul 31 11:08 archives
        // -rw------- 1 message group           1097 Jul 31 04:40 confirm.txt.1240.asc.09.07.31_04.40


        public void Run()
        {
            if (!CheckDirectory(String.Empty))
            {
                Log("CheckDirectory", MLX_ARCHIVE_DIR, "Archive directory does not exist : " + MLX_ARCHIVE_DIR);
                LogError("Archive directory does not exist : " + MLX_ARCHIVE_DIR);
                return;
            }

            if (!JuicyLib.Net.FTP.JuicyFTP.IsFTPUri(MLX_HOST))
            {
                Log("CheckURI", MLX_HOST, "FTP host is a malformed Uri : " + MLX_HOST);
                LogError("FTP host is a malformed Uri : " + MLX_HOST);
                return;
            }

            List<string> fileList;

            if (m_FTP.TryGetFileList(out fileList))
            {
                foreach(string fileName in fileList)
                {
                    ProcessFile(fileName);
                }
            }

        }


        private bool CheckDirectory(string subDirectory)
        {
            if (subDirectory != String.Empty && subDirectory[0] == '/')
                subDirectory = subDirectory.Substring(1);

            string directory = MLX_ARCHIVE_DIR + subDirectory;

            try
            {
                if (Directory.Exists(directory))
                    return true;

                Directory.CreateDirectory(directory);
                return true;
            }
            catch (Exception e)
            {
                Log("CheckDirectory", MLX_ARCHIVE_DIR, "Create directory failed with message : " + e.Message);
                return false;
            }
        }

        private bool CheckFileExists(string directory, string fileName)
        {
            if (fileName != String.Empty && fileName[0] == '/')
                fileName = fileName.Substring(1);

            if (directory[directory.Length - 1] != '/')
                directory = directory + '/';

            if (File.Exists(directory + fileName))
                return true;

            return false;
        }


        private void ProcessFile(string fileName)
        {
            string destDir;
            string baseFilename;
            string encryptedData;

            // get/create actual directory for this file
            // depending on its date
            if(GetDirPath(fileName, out destDir))
            {
                // final destination directory to save this file
                destDir = MLX_ARCHIVE_DIR + destDir;
                // final file name to save the file
                baseFilename = fileName.Substring(0, fileName.IndexOf(ML_FILENAME_ASC_SUFFIX));

                // check we don't already have this file
                if (CheckFileExists(destDir, baseFilename))
                {
                    Console.WriteLine("Skipping file : " + destDir + "/" + baseFilename);
                }
                else
                {
                    // get the file from MLX
                    if (m_FTP.GetFileAsString(fileName, out encryptedData))
                    {
                        string tempFile = Path.GetTempFileName();

                        File.AppendAllText(tempFile, encryptedData);
                        DecryptFile(tempFile, destDir + "/" + baseFilename);
                        Log("ProcessFile", destDir + "/" + baseFilename, "Decrypted and saved file : " + destDir + "/" + baseFilename);
                    }
                    else
                    {
                        Console.WriteLine("Failed to get file : " + destDir + "/" + baseFilename);
                    }
                }
            }
            else
            {
                Log("ProcessFile", destDir, "Failed to get dir : " + destDir);
            }
        }
        
        private bool GetDirPath(string fileName, out string dirName)
        {
            string date = fileName.Substring(fileName.Length - ML_FILENAME_DATE_LEN, ML_FILENAME_DATE_LEN);
            string[] parts = date.Split('_');
            string[] dateParts = parts[0].Split('.');
            string[] timeParts = parts[1].Split('.');

            dirName = DIR_CENT_PREFIX + String.Join("/", dateParts);

            if (CheckDirectory(dirName))
                return true;

            return false;
        }


        private void DecryptFile(string sourceFile, string destinationFile)
        {
            System.Diagnostics.ProcessStartInfo psi =
              new System.Diagnostics.ProcessStartInfo("cmd.exe");
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;            
            psi.RedirectStandardError = true;
            psi.WorkingDirectory = Path.GetDirectoryName(GPG_DIR);

            System.Diagnostics.Process process = System.Diagnostics.Process.Start(psi);
            // actually NEED to set this as a local string variable
            // and pass it - bombs otherwise!
            string sCommandLine = "\"" + GPG_BIN + "\" -o \"" +
                                destinationFile + "\" --decrypt \"" +
                                sourceFile + "\"";
            process.StandardInput.WriteLine(sCommandLine);
            process.StandardInput.Flush();
            process.StandardInput.Close();
            
            //Console.WriteLine(process.StandardError.ReadToEnd());

            process.WaitForExit();
            process.Close();            
        }


        private bool DecryptStream(string dataIn, out string dataOut)
        {
            GnuPGWrapper gpg = new GnuPGWrapper();
            gpg.homedirectory = GPG_DIR;
            gpg.command = Commands.Decrypt;
            gpg.ExecuteCommand(dataIn, out dataOut);
            return true;

            try
            {
                //m_GPG.ExecuteCommand(dataIn, out dataOut);
                return true;
            }
            catch
            {
                dataOut = "FAILED!";
                return false;
            }
        }


        private bool SaveString(string directory, string fileName, string data)
        {
            if (fileName != String.Empty && fileName[0] == '/')
                fileName = fileName.Substring(1);

            if (directory[directory.Length - 1] != '/')
                directory = directory + '/';

            try
            {
                File.AppendAllText(directory + fileName, data);
                return true;
            }
            catch
            {
                return false;
            }
        }
    }
}
